home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / DELPHI / EZDSL200.ZIP / EZRNDSTM.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-03-13  |  15.8 KB  |  588 lines

  1. {===EZRndStm==========================================================
  2.  
  3. This unit provides a set of routines to implement random number
  4. streams. A random number stream is an independent random sequence
  5. generator, implying than more than one can exist in a program. The
  6. basic generator creates uniformly distributed pseudo-random numbers.
  7.  
  8. To use a random number stream, you first create a new one by calling
  9. CreateRandStream. If you pass zero as the initial seed this will
  10. randomize the stream based on the system clock (equivalent to
  11. System.Randomize), otherwise the stream will be initialized to a known
  12. repeatable state. Then call rsRandom (or rsRandomWord or
  13. rsRandomLongint) to get the next random number from the stream
  14. (rsRandom is equivalent to System.Random). Once you have finished with
  15. the random number stream you call DestroyRandStream and this will free
  16. any memory allocated to the stream.
  17.  
  18. If you wish to reinitialize a random number stream, destroy it and
  19. create a new one. You can have as many random number streams as memory
  20. will allow, each takes 222 bytes of heap.
  21.  
  22. The ancillary unit EZRNDDST uses these random streams to generate
  23. random numbers with other distributions.
  24.  
  25. According to Knuth the internal generator has a cycle of 2^55 - 1.
  26. It's also slightly faster than the standard System unit one, and uses
  27. no data segment space.
  28.  
  29. References
  30.   Random bit generator from Numerical Recipes in Pascal
  31.   Random table algorithm from Sedgewick: Algorithms
  32.   Random number test algorithms from Knuth: Seminumerical Algorithms
  33.   Other distributions from Knuth again, and
  34.        Watkins: Discrete Event Simulation in C
  35.  
  36. EZRndStm is Copyright (c) 1994,1996 Julian M. Bucknall
  37.  
  38. VERSION HISTORY
  39. 13Mar96 JMB 2.00 release for Delphi 2.0
  40. 18Jun95 JMB 1.00 initial release
  41. ======================================================================}
  42.  
  43. unit EZRndStm;
  44.  
  45. { Undefine this if you don't want debugging info }
  46. {.$DEFINE DEBUG}
  47.  
  48. {$IFNDEF VER70}
  49. {$IFNDEF VER80} {$IFNDEF VER90}
  50. !! Error - this unit is for BP7 and Delphi 1.0/2.0 only
  51. {$ENDIF} {$ENDIF}
  52. {$ENDIF}
  53.  
  54. {$DEFINE Delphi}
  55. {$IFDEF VER70}
  56. {$UNDEF Delphi}
  57. {$ENDIF}
  58.  
  59. {------Fixed compiler switches----------------------------------------}
  60. {$B-   Short-circuit boolean expressions }
  61. {$IFNDEF VER90}
  62. {$G+   80286+ type instructions }
  63. {$ENDIF}
  64. {$V-   Disable var string checking }
  65. {$W-   No Windows realmode stack frame }
  66. {$X+   Enable extended syntax }
  67. {$IFDEF DEBUG}
  68. {$D+,L+  Enable debug information }
  69. {$ELSE}
  70. {$D-,L-  Disable debug information }
  71. {$ENDIF}
  72. {---------------------------------------------------------------------}
  73.  
  74. INTERFACE
  75.  
  76. {$IFDEF Win32}
  77. uses
  78.   SysUtils, Windows;
  79. {$ELSE}
  80. {$IFDEF VER80}
  81. uses
  82.   SysUtils;
  83. {$ENDIF}
  84. {$ENDIF}
  85.  
  86. const
  87.   rsTableEntries = 55;
  88.  
  89. type
  90.   {A random number stream}
  91.   PRandStream = ^TRandStream;
  92.   TRandStream = record
  93.     Table : array [0..pred(rsTableEntries)] of longint;
  94.     Offset: integer;
  95.   end;
  96.  
  97.   {Definition of a floating point value}
  98.   {$IFDEF Win32}
  99.   TrsFloat = extended;
  100.   {$ELSE}
  101.   {$IFOPT N+}
  102.   TrsFloat = extended;
  103.   {$ELSE}
  104.   TrsFloat = real;
  105.   {$ENDIF}
  106.   {$ENDIF}
  107.  
  108.   {Numerical parameter errors}
  109.   TrsError = (rsErrNone,                {No error}
  110.               rsErrBadMean,             {Invalid mean}
  111.               rsErrBadVariance,         {Invalid variance}
  112.               rsErrBadStdDev,           {Invalid standard deviation}
  113.               rsErrBadRange,            {Invalid range}
  114.               rsErrBadOrder,            {Bad order}
  115.               rsErrInvProb);            {Invalid probability}
  116.  
  117.   {Error handler prototype}
  118.   TrsErrorHandler = procedure (Error : TrsError);
  119.  
  120. {=rsErrorHandler======================================================
  121. The unit's error handler. The default one raises an exception under
  122. Delphi, and halts the program with runtime error 207 in other Borland
  123. Pascal versions.
  124. Replace with your own error handler if you want this behaviour to
  125. change.
  126. 18Jun95 JMB
  127. ======================================================================}
  128. const
  129.   rsErrorHandler : TrsErrorHandler = nil;
  130.  
  131. {=CreateRandStream====================================================
  132. Creates a new random number stream on the heap. Seed is the initial
  133. value to seed the internal table. If this is zero, the seed is taken
  134. from the system clock.
  135. 18Jun95 JMB
  136. ======================================================================}
  137. function  CreateRandStream(Seed : longint) : PRandStream;
  138.  
  139. {=DestroyRandStream===================================================
  140. Destroys a random number stream initialized by CreateRandStream. Sets
  141. the RS parameter to nil.
  142. 18Jun95 JMB
  143. ======================================================================}
  144. procedure DestroyRandStream(var RS : PRandStream);
  145.  
  146. {=rsRandom============================================================
  147. Returns the next random number from the passed random number stream.
  148. The value is between 0 and pred(UpperLimit).
  149. 18Jun95 JMB
  150. ======================================================================}
  151. function  rsRandom(RS : PRandStream; UpperLimit : word) : Cardinal;
  152.  
  153. {=rsRandomWord========================================================
  154. Returns the next random number from the passed random number stream.
  155. The value is a full 16-bit word, ie between 0 and 65535 inclusive.
  156. 18Jun95 JMB
  157. ======================================================================}
  158. function  rsRandomWord(RS : PRandStream) : word;
  159.  
  160. {=rsRandomLongint=====================================================
  161. Returns the next random number from the passed random number stream.
  162. The value is a full 31-bit non-negative longint between 0 and
  163. pred(2^31) inclusive.
  164. 18Jun95 JMB
  165. ======================================================================}
  166. function  rsRandomLongint(RS : PRandStream) : longint;
  167.  
  168. {=rsRandomFloat=======================================================
  169. Returns the next random number from the passed random number stream.
  170. The value is uniformly distributed in the range: 0.0 <= x < 1.0.
  171. 18Jun95 JMB
  172. ======================================================================}
  173. function  rsRandomFloat(RS : PRandStream) : TrsFloat;
  174.  
  175. {=rsRandomUniform=====================================================
  176. Returns the next random number from the passed random number stream.
  177. The value is uniformly distributed in the range: Lower <= x < Upper.
  178. Will call rsErrorHandler with rsErrBadRange if Upper <= Lower.
  179. 18Jun95 JMB
  180. ======================================================================}
  181. function rsdRandomUniform(RS : PRandStream; Lower, Upper : TrsFloat) : TrsFloat;
  182.  
  183. IMPLEMENTATION
  184.  
  185. {Note: the Offset field is a byte offset, not an element offset}
  186.  
  187. {$IFDEF Win32}
  188. var
  189. {$ELSE}
  190. const
  191. {$ENDIF}
  192.   IsUnitInitialised : boolean = false;
  193.  
  194. const
  195.   TableMagic   = 23;
  196.  
  197. {=DefErrorHandler=====================================================
  198. Default error handler: just exits with run time error 207.
  199. 18Jun95 JMB
  200. ======================================================================}
  201. procedure DefErrorHandler(Error : TrsError); far;
  202.   begin
  203.     {$IFDEF VER80}
  204.     Raise(Exception.Create('Random number generator error'));
  205.     {$ELSE}
  206.     RunError(207); {invalid floating point operation}
  207.     {$ENDIF}
  208.   end;
  209.  
  210. {=InitRSUnit==========================================================
  211. Initialises the unit: sets the default error handler.
  212. 18Jun95 JMB
  213. ======================================================================}
  214. procedure InitRSUnit;
  215.   begin
  216.     if not Assigned(rsErrorHandler) then
  217.       rsErrorHandler := DefErrorHandler;
  218.  
  219.     IsUnitInitialised := true;
  220.   end;
  221.  
  222. {$IFDEF Win32}
  223. {=Random32Bit=========================================================
  224. Generates a random 32-bit word value, bit by bit. Used for seeding a
  225. random number generator table, should NOT be used as a direct random
  226. number.
  227.  
  228. Based on the Primitive Polynominal Mod 2: (32, 7, 5, 3, 2, 1, 0).
  229.  
  230. Input:  EBX = current seed
  231. Output: EBX = new seed
  232.         EAX = 32-bit random value
  233.         ECX, EDX trashed
  234.  
  235. 13Mar96 JMB
  236. ======================================================================}
  237. procedure Random32Bit;
  238.   register;
  239.   asm
  240.     mov ecx, 32         {use ecx as the count}
  241.   @@NextBit:
  242.     mov edx, ebx
  243.     mov eax, edx        {get bit 0 of seed}
  244.     shr edx, 1          {xor with bit 1 of seed}
  245.     xor eax, edx
  246.     shr edx, 1          {xor with bit 2 of seed}
  247.     xor eax, edx
  248.     shr edx, 2          {xor with bit 4 of seed}
  249.     xor eax, edx
  250.     shr edx, 2          {xor with bit 6 of seed}
  251.     xor eax, edx
  252.     shr edx, 25         {xor with bit 31 of seed}
  253.     xor eax, edx
  254.     and eax, 1          {isolate the new random bit}
  255.     shl ebx, 1          {shift seed left by one}
  256.     or ebx, eax         {add in the new bit to the seed as bit 0}
  257.     dec ecx             {go get next random bit, until we've got them all}
  258.     jnz @@NextBit
  259.     mov eax, ebx        {return random bits}
  260.   end;
  261. {$ELSE}
  262. {=Random16Bit=========================================================
  263. Generates a random 16-bit word value, bit by bit. Used for seeding a
  264. random number generator table, should NOT be used as a direct random
  265. number.
  266.  
  267. Based on the Primitive Polynominal Mod 2: (32, 7, 5, 3, 2, 1, 0).
  268.  
  269. Input:  DX:BX = current seed
  270. Output: DX:BX = new seed
  271.         AX    = 16-bit random value
  272.         CX, SI, DI trashed
  273.  
  274. 18Jun95 JMB
  275. ======================================================================}
  276. procedure Random16Bit;
  277.   near; assembler;
  278.   asm
  279.     mov cx, 16          {use cx as the count}
  280.   @@NextBit:
  281.     mov si, bx
  282.     mov ax, si          {get bit 0 of seed}
  283.     shr si, 1           {xor with bit 1 of seed}
  284.     xor ax, si
  285.     shr si, 1           {xor with bit 2 of seed}
  286.     xor ax, si
  287.     shr si, 1           {xor with bit 4 of seed}
  288.     shr si, 1
  289.     xor ax, si
  290.     shr si, 1           {xor with bit 6 of seed}
  291.     shr si, 1
  292.     xor ax, si
  293.     mov si, dx          {xor with bit 31 of seed}
  294.     shl si, 1
  295.     rcl si, 1
  296.     xor ax, si
  297.     and ax, 1           {isolate the new random bit}
  298.     shl bx, 1           {shift seed left by one}
  299.     rcl dx, 1
  300.     or bx, ax           {add in the new bit to the seed as bit 0}
  301.     loop @@NextBit      {go get next random bit, until we've got them all}
  302.     mov ax, bx          {return random bits as a word}
  303.   end;
  304. {$ENDIF}
  305.  
  306. {=InitTable===========================================================
  307. Uses Random16Bit/Random32Bit to seed the random number generator
  308. table.
  309. 13Mar96 JMB
  310. ======================================================================}
  311. {$IFDEF Win32}
  312. procedure InitTable(RS : PRandStream; Seed : longint);
  313.   register;
  314.   asm
  315.     push edi
  316.     push ebx
  317.     mov edi, eax
  318.     mov ecx, rsTableEntries
  319.     mov ebx, edx
  320.   @@NextEntry:
  321.     push ecx
  322.     call Random32Bit
  323.     pop ecx
  324.     stosd
  325.     dec ecx
  326.     jnz @@NextEntry
  327.     and eax, $1F
  328.     shl eax, 2
  329.     stosd
  330.     pop ebx
  331.     pop edi
  332.   end;
  333. {$ELSE}
  334. procedure InitTable(RS : PRandStream; Seed : longint);
  335.   near; assembler;
  336.   asm
  337.     push ds
  338.     lds di, RS
  339.     mov ax, ds
  340.     mov es, ax
  341.     mov cx, rsTableEntries * 2
  342.     cld
  343.     mov dx, Seed.Word[2]
  344.     mov bx, Seed.Word[0]
  345.   @@NextWord:
  346.     push di
  347.     push cx
  348.     call Random16Bit
  349.     pop cx
  350.     pop di
  351.     stosw
  352.     loop @@NextWord
  353.     and ax, $1F
  354.     shl ax, 1
  355.     shl ax, 1
  356.     stosw
  357.     pop ds
  358.   end;
  359. {$ENDIF}
  360.  
  361. function  CreateRandStream(Seed : longint) : PRandStream;
  362.   var
  363.     RS : PRandStream;
  364.   begin
  365.     GetMem(RS, sizeof(TRandStream));
  366.     if Assigned(RS) then
  367.       begin
  368.         if (Seed = 0) then
  369.           {$IFDEF Win32}
  370.           Seed := GetTickCount;
  371.           {$ELSE}
  372.           asm
  373.             mov ah, $2C
  374.             int $21
  375.             mov Seed.Word[0], cx
  376.             mov Seed.Word[2], dx
  377.           end;
  378.           {$ENDIF}
  379.         InitTable(RS, Seed);
  380.         if not IsUnitInitialised then
  381.           InitRSUnit;
  382.       end;
  383.     CreateRandStream := RS;
  384.   end;
  385.  
  386. procedure DestroyRandStream(var RS : PRandStream);
  387.   begin
  388.     if Assigned(RS) then
  389.       begin
  390.         FreeMem(RS, sizeof(TRandStream));
  391.         RS := nil;
  392.       end;
  393.   end;
  394.  
  395. {$IFDEF Win32}
  396. function GetNextRandomLong(RS : PRandStream) : longint;
  397.   {Input:  eax = RS
  398.    Output: eax = random long word
  399.            ecx, edx trashed}
  400.   register;
  401.   asm
  402.     mov ecx, eax
  403.     mov edx, [ecx].TRandStream.&Offset
  404.     mov eax, [ecx+edx]
  405.     sub edx, 4
  406.     jge @@1
  407.     mov edx, rsTableEntries * 4 - 4
  408.   @@1:
  409.     push edx
  410.     sub edx, TableMagic * 4
  411.     jge @@2
  412.     add edx, rsTableEntries * 4
  413.   @@2:
  414.     add eax, [ecx+edx]
  415.     pop edx
  416.     mov [ecx+edx], eax
  417.     mov [ecx].TRandStream.&Offset, edx
  418.   end;
  419.  
  420. function rsRandom(RS : PRandStream; UpperLimit : word) : Cardinal;
  421.   {Input:  eax = RS
  422.            edx = UpperLimit
  423.    Output: ax = random value}
  424.   register;
  425.   asm
  426.     movzx edx, dx
  427.     push edx
  428.     call GetNextRandomLong
  429.     pop edx
  430.     mul edx
  431.     mov eax, edx
  432.   end;
  433.  
  434. function rsRandomWord(RS : PRandStream) : word;
  435.   {Input:  eax = RS
  436.    Output: ax = random value}
  437.   register;
  438.   asm
  439.     call GetNextRandomLong
  440.     shr eax, 16
  441.   end;
  442.  
  443. function rsRandomLongint(RS : PRandStream) : longint; assembler;
  444.   {Input:  eax = RS
  445.    Output: eax = random value}
  446.   register;
  447.   asm
  448.     call GetNextRandomLong
  449.     shr eax, 1
  450.   end;
  451.  
  452. function  rsRandomFloat(RS : PRandStream) : TrsFloat; assembler;
  453.   {Input:  eax = RS
  454.    Output: random value on floating point stack}
  455.   register;
  456.   const
  457.     Scale : integer = -31;
  458.   asm
  459.     call GetNextRandomLong
  460.     shr eax, 1
  461.     fild Scale
  462.     push eax
  463.     fild dword ptr [esp]
  464.     add esp, 4
  465.     fscale
  466.     fstp st(1)
  467.   end;
  468. {$ELSE}
  469. procedure GetNextRandomLong;
  470.   {-Input:  ds:si   => TRandStream
  471.     Output: dx:ax   new random longint
  472.             ds, si  unchanged
  473.             bx, di  trashed
  474.             cx      not touched}
  475.   near; assembler;
  476.   asm
  477.     mov bx, [si].TRandStream.&Offset
  478.     mov ax, [si+bx]
  479.     mov dx, [si+bx+2]
  480.     sub bx, 4
  481.     jge @@1
  482.     mov bx, rsTableEntries * 4 - 4
  483.   @@1:
  484.     mov di, bx
  485.     sub bx, TableMagic * 4
  486.     jge @@2
  487.     add bx, rsTableEntries * 4
  488.   @@2:
  489.     add ax, [si+bx]
  490.     adc dx, [si+bx+2]
  491.     mov bx, di
  492.     mov [si+bx], ax
  493.     mov [si+bx+2], dx
  494.     mov [si].TRandStream.&Offset, bx
  495.   end;
  496.  
  497. function rsRandom(RS : PRandStream; UpperLimit : word) : word;
  498.   assembler;
  499.   asm
  500.     mov cx, ds
  501.     lds si, RS
  502.     call GetNextRandomLong
  503.     xchg ax, dx
  504.     mul UpperLimit
  505.     xchg ax, dx
  506.     mov ds, cx
  507.   end;
  508.  
  509. function rsRandomWord(RS : PRandStream) : word;
  510.   assembler;
  511.   asm
  512.     mov cx, ds
  513.     lds si, RS
  514.     call GetNextRandomLong
  515.     xchg ax, dx
  516.     mov ds, cx
  517.   end;
  518.  
  519. function rsRandomLongint(RS : PRandStream) : longint;
  520.   assembler;
  521.   asm
  522.     mov cx, ds
  523.     lds si, RS
  524.     call GetNextRandomLong
  525.     shr dx, 1
  526.     rcr ax, 1
  527.     mov ds, cx
  528.   end;
  529.  
  530. function  rsRandomFloat(RS : PRandStream) : TrsFloat;
  531.   assembler;
  532. {$IFOPT N+}
  533.   var
  534.     R : longint;
  535.     Scale : integer;
  536.   asm
  537.     mov cx, ds
  538.     lds si, RS
  539.     call GetNextRandomLong
  540.     shr dx, 1
  541.     rcr ax, 1
  542.     mov R.Word[0], ax
  543.     mov R.Word[2], dx
  544.     mov Scale, -31
  545.     fild Scale
  546.     fild R
  547.     fscale
  548.     fstp st(1)
  549.     fwait
  550.     mov ds, cx
  551.   end;
  552. {$ELSE} {N-}
  553.   asm
  554.     mov cx, ds
  555.     lds si, RS
  556.     call GetNextRandomLong
  557.     mov bx, ax
  558.     or ax, dx
  559.     jz @@Exit
  560.     mov ax, $80
  561.     jmp @@StartNormalize
  562.   @@MultBy2:
  563.     shl bx, 1
  564.     rcl dx, 1
  565.     dec al
  566.   @@StartNormalize:
  567.     test dh, $80
  568.     jz @@MultBy2
  569.     and dh, $7F
  570.   @@Exit:
  571.     mov ds, cx
  572.   end;
  573. {$ENDIF}
  574. {$ENDIF}
  575.  
  576. function rsdRandomUniform(RS : PRandStream; Lower, Upper : TrsFloat) : TrsFloat;
  577.   begin
  578.     if (Upper <= Lower) then
  579.       begin
  580.         rsErrorHandler(rsErrBadRange);
  581.         rsdRandomUniform := 0.0;
  582.       end
  583.     else
  584.       rsdRandomUniform := (rsRandomFloat(RS) * (Upper - Lower)) + Lower;
  585.   end;
  586.  
  587. end.
  588.